/*
 * $Id: PDFPage.java,v 1.5 2007/09/22 12:58:40 gil1 Exp $ $Date: 2007/09/22
 * 12:58:40 $ This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the License,
 * or (at your option) any later version. This library is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
 * the GNU Lesser General Public License for more details. You should have
 * received a copy of the GNU Lesser General Public License along with this
 * library; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place, Suite 330, Boston, MA 02111-1307 USA
 */
package gnu.jpdf;

import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.print.PageFormat;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.Vector;

/**
 * <p>
 * This class defines a single page within a document. It is linked to a single
 * PDFGraphics object
 * </p>
 * 
 * @author Peter T Mount
 * @author Eric Z. Beard, ericzbeard@hotmail.com
 * @author Gilbert DeLeeuw, gil1@users.sourceforge.net
 * @version $Revision: 1.5 $, $Date: 2007/09/22 12:58:40 $
 */
public class PDFPage extends PDFObject implements Serializable
{
    /*
     * NOTE: The original class is the work of Peter T. Mount, who released it
     * in the uk.org.retep.pdf package. It was modified by Eric Z. Beard as
     * follows: The package name was changed to gnu.pdf. The formatting was
     * changed a little bit. It is still licensed under the LGPL.
     */

    /**
     * Default page format (Letter size with 1 inch margins and Portrait
     * orientation)
     */
    private static final PageFormat DEF_FORMAT = new PageFormat();
    
    /**
     * This is this page format, ie the size of the page, margins, and rotation
     */
    protected PageFormat pageFormat;
    
    /**
     * This is the pages object id that this page belongs to. It is set by the
     * pages object when it is added to it.
     */
    protected PDFObject pdfPageList;
    
    /**
     * This holds the contents of the page.
     */
    protected Vector<PDFObject> contents;
    
    /**
     * Object ID that contains a thumbnail sketch of the page. -1 indicates no
     * thumbnail.
     */
    protected PDFObject thumbnail;
    
    /**
     * This holds any Annotations contained within this page.
     */
    protected Vector<PDFObject> annotations;
    
    /**
     * This holds any resources for this page
     */
    protected Vector<String> resources;
    
    // JM
    protected Vector<String> imageResources;
    
    /**
     * The fonts associated with this page
     */
    protected Vector<PDFFont> fonts;
    
    /**
     * The xobjects or other images in the pdf
     */
    // protected Vector xobjects;
    
    /**
     * These handle the procset for this page. Refer to page 140 of the PDF
     * Reference manual NB: Text is handled when the fonts Vector is null, and a
     * font is created refer to getFont() to see where it's defined
     */
    protected boolean hasImageB, hasImageC, hasImageI;
    protected procset procset;
    
    /**
     * This constructs a Page object, which will hold any contents for this
     * page.
     * <p>
     * Once created, it is added to the document via the PDF.add() method. (For
     * Advanced use, via the PDFPages.add() method).
     * <p>
     * This defaults to a4 media.
     */
    public PDFPage()
    {
        super("/Page");
        pageFormat = DEF_FORMAT;
        contents = new Vector<PDFObject>();
        thumbnail = null;
        annotations = new Vector<PDFObject>();
        resources = new Vector<String>();
        // JM
        imageResources = new Vector<String>();
        fonts = new Vector<PDFFont>();
        procset = null;
    }
    
    /**
     * Constructs a page using A4 media, but using the supplied orientation.
     * 
     * @param orientation
     *            Orientation: 0, 90 or 270
     * @see PageFormat#PORTRAIT
     * @see PageFormat#LANDSCAPE
     * @see PageFormat#REVERSE_LANDSCAPE
     */
    public PDFPage(int orientation)
    {
        this();
        setOrientation(orientation);
    }
    
    /**
     * Constructs a page using the supplied media size and orientation.
     * 
     * @param pageFormat
     *            PageFormat describing the page size
     */
    public PDFPage(PageFormat pageFormat)
    {
        this();
        this.pageFormat = pageFormat;
    }
    
    /**
     * Adds to procset.
     * 
     * @param proc
     *            the String to be added.
     */
    public void addToProcset(String proc)
    {
        if (procset == null)
        {
            addProcset();
        }
        procset.add(proc);
    }
    
    /**
     * This returns a PDFGraphics object, which can then be used to render on to
     * this page. If a previous PDFGraphics object was used, this object is
     * appended to the page, and will be drawn over the top of any previous
     * objects.
     * 
     * @return a new PDFGraphics object to be used to draw this page.
     */
    public PDFGraphics getGraphics()
    {
        try
        {
            PDFGraphics g = new PDFGraphics();
            g.init(this);
            return g;
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
        
        return null;
    }
    
    /**
     * Returns a PDFFont, creating it if not yet used.
     * 
     * @param type
     *            Font type, usually /Type1
     * @param font
     *            Font name
     * @param style
     *            java.awt.Font style, ie Font.NORMAL
     * @return a PDFFont object.
     */
    public PDFFont getFont(String type, String font, int style)
    {
        // Search the fonts on this page, and return one that matches this
        // font.
        // This keeps the number of font definitions down to one per font/style
        for (PDFFont ft : fonts)
        {
            if (ft.equals(type, font, style))
                return ft;
        }
        
        // Ok, the font isn't in the page, so create one.
        
        // We need a procset if we are using fonts, so create it (if not
        // already created, and add to our resources
        if (fonts.size() == 0)
        {
            addProcset();
            procset.add("/Text");
        }
        
        // finally create and return the font
        PDFFont f = pdfDocument.getFont(type, font, style);
        fonts.addElement(f);
        return f;
    }
    
    /**
     * Returns the page's PageFormat.
     * 
     * @return PageFormat describing the page size in device units (72dpi)
     */
    public PageFormat getPageFormat()
    {
        return pageFormat;
    }
    
    /**
     * Gets the dimensions of the page.
     * 
     * @return a Dimension object containing the width and height of the page.
     */
    public Dimension getDimension()
    {
        return new Dimension((int) pageFormat.getWidth(), (int) pageFormat
                .getHeight());
    }
    
    /**
     * Gets the imageable area of the page.
     * 
     * @return a Rectangle containing the bounds of the imageable area.
     */
    public Rectangle getImageableArea()
    {
        return new Rectangle((int) pageFormat.getImageableX(), (int) pageFormat
                .getImageableY(),
                (int) (pageFormat.getImageableX() + pageFormat
                        .getImageableWidth()), (int) (pageFormat
                        .getImageableY() + pageFormat.getImageableHeight()));
    }
    
    /**
     * Sets the page's orientation.
     * <p>
     * Normally, this should be done when the page is created, to avoid
     * problems.
     * 
     * @param orientation
     *            a PageFormat orientation constant: PageFormat.PORTRAIT,
     *            PageFormat.LANDSACPE or PageFromat.REVERSE_LANDSACPE
     */
    public void setOrientation(int orientation)
    {
        pageFormat.setOrientation(orientation);
    }
    
    /**
     * Returns the pages orientation: PageFormat.PORTRAIT, PageFormat.LANDSACPE
     * or PageFromat.REVERSE_LANDSACPE
     * 
     * @see java.awt.print.PageFormat
     * @return current orientation of the page
     */
    public int getOrientation()
    {
        return pageFormat.getOrientation();
    }
    
    /**
     * This adds an object that describes some content to this page.
     * <p>
     * <b>Note:</b> Objects that describe contents must be added using this
     * method _AFTER_ the PDF.add() method has been called.
     * 
     * @param ob
     *            PDFObject describing some contents
     */
    public void add(PDFObject ob)
    {
        contents.addElement(ob);
    }
    
    /**
     * This adds an Annotation to the page.
     * <p>
     * As with other objects, the annotation must be added to the pdf document
     * using PDF.add() before adding to the page.
     * 
     * @param ob
     *            Annotation to add.
     */
    public void addAnnotation(PDFObject ob)
    {
        annotations.addElement(ob);
    }
    
    /**
     * This method adds a text note to the document.
     * 
     * @param note
     *            Text of the note
     * @param x
     *            Coordinate of note
     * @param y
     *            Coordinate of note
     * @param w
     *            Width of the note
     * @param h
     *            Height of the note
     * @return Returns the annotation, so other settings can be changed.
     */
    public PDFAnnot addNote(String note, int x, int y, int w, int h)
    {
        int xy1[] = cxy(x, y + h);
        int xy2[] = cxy(x + w, y);
        PDFAnnot ob = new PDFAnnot(xy1[0], xy1[1],
                                   xy2[0], xy2[1],
                                   note);
        pdfDocument.add(ob);
        annotations.addElement(ob);
        return ob;
    }
    
    /**
     * Adds a hyperlink to the document.
     * 
     * @param x
     *            Coordinate of active area
     * @param y
     *            Coordinate of active area
     * @param w
     *            Width of the active area
     * @param h
     *            Height of the active area
     * @param dest
     *            Page that will be displayed when the link is activated. When
     *            displayed, the zoom factor will be changed to fit the display.
     * @return Returns the annotation, so other settings can be changed.
     */
    public PDFAnnot addLink(int x, int y, int w, int h, PDFObject dest)
    {
        int xy1[] = cxy(x, y + h);
        int xy2[] = cxy(x + w, y);
        PDFAnnot ob = new PDFAnnot(xy1[0], xy1[1],
                                   xy2[0], xy2[1],
                                   dest
                                   );
        pdfDocument.add(ob);
        annotations.addElement(ob);
        return ob;
    }
    
    /**
     * Adds a hyperlink to the document.
     * 
     * @param x
     *            Coordinate of active area
     * @param y
     *            Coordinate of active area
     * @param w
     *            Width of the active area
     * @param h
     *            Height of the active area
     * @param dest
     *            Page that will be displayed when the link is activated
     * @param vx
     *            Coordinate of view area
     * @param vy
     *            Coordinate of view area
     * @param vw
     *            Width of the view area
     * @param vh
     *            Height of the view area
     * @return Returns the annotation, so other settings can be changed.
     */
    public PDFAnnot addLink(int x, int y, int w, int h,
                            PDFObject dest,
                            int vx, int vy, int vw, int vh)
    {
        int xy1[] = cxy(x, y + h);
        int xy2[] = cxy(x + w, y);
        int xy3[] = cxy(vx, vy + vh);
        int xy4[] = cxy(vx + vw, vy);
        PDFAnnot ob = new PDFAnnot(xy1[0], xy1[1],
                                   xy2[0], xy2[1],
                                   dest,
                                   xy3[0], xy3[1],
                                   xy4[0], xy4[1]
                                   );
        pdfDocument.add(ob);
        annotations.addElement(ob);
        return ob;
    }
    
    /** Contains the text strings for the xobjects. */
    private Vector<String> xobjects = new Vector<String>();
    
    /**
     * This adds an XObject resource to the page. The string should be of the
     * format /Name ObjectNumber RevisionNumber R as in /Image1 13 0 R .
     * 
     * @param inxobject
     *            the XObject resource to be added.
     */
    public void addXObject(String inxobject)
    {
        xobjects.addElement(inxobject);
    }
    
    /**
     * This adds a resource to the page.
     * 
     * @param resource
     *            String defining the resource
     */
    public void addResource(String resource)
    {
        resources.addElement(resource);
    }
    
    // JM
    /**
     * This adds an image resource to the page.
     * 
     * @param resource
     *            the XObject resource to be added.
     */
    public void addImageResource(String resource)
    {
        imageResources.addElement(resource);
    }
    
    /**
     * This adds an object that describes a thumbnail for this page.
     * <p>
     * <b>Note:</b> The object must already exist in the PDF, as only the object
     * ID is stored.
     * 
     * @param thumbnail
     *            PDFObject containing the thumbnail
     */
    public void setThumbnail(PDFObject thumbnail)
    {
        this.thumbnail = thumbnail;
    }
    
    /**
     * This method attaches an outline to the current page being generated. When
     * selected, the outline displays the top of the page.
     * 
     * @param title
     *            Outline title to attach
     * @return PDFOutline object created, for addSubOutline if required.
     */
    public PDFOutline addOutline(String title)
    {
        PDFOutline outline = new PDFOutline(title, this);
        pdfDocument.add(outline);
        pdfDocument.getOutline().add(outline);
        return outline;
    }
    
    /**
     * This method attaches an outline to the current page being generated. When
     * selected, the outline displays the top of the page.
     * <p>
     * Note: If the outline is not in the top level (ie below another outline)
     * then it must <b>not</b> be passed to this method.
     * 
     * @param title
     *            Outline title to attach
     * @param x
     *            Left coordinate of region
     * @param y
     *            Bottom coordinate of region
     * @param w
     *            Width of region
     * @param h
     *            Height coordinate of region
     * @return PDFOutline object created, for addSubOutline if required.
     */
    public PDFOutline addOutline(String title, int x, int y, int w, int h)
    {
        int xy1[] = cxy(x, y + h);
        int xy2[] = cxy(x + w, y);
        PDFOutline outline = new PDFOutline(title, this,
                                            xy1[0], xy1[1],
                                            xy2[0], xy2[1]);
        pdfDocument.add(outline);
        pdfDocument.getOutline().add(outline);
        return outline;
    }
    
    /**
     * @param os
     *            OutputStream to send the object to
     * @exception IOException
     *                on error
     */
    public void write(OutputStream os) throws IOException
    {
        // Write the object header
        writeStart(os);
        
        // now the objects body
        
        // the /Parent pages object
        os.write("/Parent ".getBytes());
        os.write(pdfPageList.toString().getBytes());
        os.write("\n".getBytes());
        
        // the /MediaBox for the page size
        os.write("/MediaBox [".getBytes());
        os.write(Integer.toString(0).getBytes());
        os.write(" ".getBytes());
        os.write(Integer.toString(0).getBytes());
        os.write(" ".getBytes());
        os.write(Integer.toString((int) pageFormat.getWidth()).getBytes());
        os.write(" ".getBytes());
        os.write(Integer.toString((int) pageFormat.getHeight()).getBytes());
        os.write("]\n".getBytes());
        
        // Rotation (if not zero)
        // if(rotate!=0) {
        // os.write("/Rotate ".getBytes());
        // os.write(Integer.toString(rotate).getBytes());
        // os.write("\n".getBytes());
        // }
        
        // Now the resources
        os.write("/Resources << ".getBytes());
        // fonts
        if (fonts.size() > 0)
        {
            // os.write("/Font << ".getBytes());
            os.write("\n/Font << ".getBytes());
            for (PDFFont font : fonts)
            {
                os.write(font.getName().getBytes());
                os.write(" ".getBytes());
                os.write(font.toString().getBytes());
                os.write(" ".getBytes());
            }
            os.write(">> ".getBytes());
        }
        // Now the XObjects
        if (xobjects.size() > 0)
        {
            os.write("\n/XObject << ".getBytes());
            for (String str : xobjects)
            {
                os.write(str.getBytes());
                os.write(" ".getBytes());
            }
            os.write(">> ".getBytes());
        }
        // Any other resources
        for (String str : resources)
        {
            os.write(str.getBytes());
            os.write(" ".getBytes());
        }
        // JM
        if (imageResources.size() > 0)
        {
            os.write("/XObject << ".getBytes());
            for (String str : imageResources)
            {
                os.write(str.getBytes());
                os.write(" ".getBytes());
            }
            os.write(" >> ".getBytes());
        }
        os.write(">>\n".getBytes());
        
        // The thumbnail
        if (thumbnail != null)
        {
            os.write("/Thumb ".getBytes());
            os.write(thumbnail.toString().getBytes());
            os.write("\n".getBytes());
        }
        
        // the /Contents pages object
        if (contents.size() > 0)
        {
            if (contents.size() == 1)
            {
                PDFObject ob = (PDFObject) contents.elementAt(0);
                os.write("/Contents ".getBytes());
                os.write(ob.toString().getBytes());
                os.write("\n".getBytes());
            }
            else
            {
                os.write("/Contents [".getBytes());
                os.write(PDFObject.toArray(contents).getBytes());
                os.write("\n".getBytes());
            }
        }
        
        // The /Annots object
        if (annotations.size() > 0)
        {
            os.write("/Annots ".getBytes());
            os.write(PDFObject.toArray(annotations).getBytes());
            os.write("\n".getBytes());
        }
        
        // finish off with its footer
        writeEnd(os);
    }
    
    /**
     * This creates a procset and sets up the page to reference it
     */
    private void addProcset()
    {
        if (procset == null)
        {
            pdfDocument.add(procset = new procset());
            resources.addElement("/ProcSet " + procset);
        }
    }
    
    /**
     * This defines a procset
     */
    public class procset extends PDFObject
    {
        private Vector<String> set;
        
        /**
         * Creates a new procset object.
         */
        public procset()
        {
            super(null);
            set = new Vector<String>();
            
            // Our default procset (use addElement not add, as we dont want a
            // leading space)
            set.addElement("/PDF");
        }
        
        /**
         * @param proc
         *            Entry to add to the procset
         */
        public void add(String proc)
        {
            set.addElement(" " + proc);
        }
        
        /**
         * @param os
         *            OutputStream to send the object to
         * @exception IOException
         *                on error
         */
        public void write(OutputStream os) throws IOException
        {
            // Write the object header
            // writeStart(os);
            
            os.write(Integer.toString(objser).getBytes());
            os.write(" 0 obj\n".getBytes());
            
            // now the objects body
            os.write("[".getBytes());
            for (String str : set)
                os.write(str.getBytes());
            os.write("]\n".getBytes());
            
            // finish off with its footer
            // writeEnd(os);
            os.write("endobj\n".getBytes());
        }
    }
    
    /**
     * This utility method converts the y coordinate from Java to User space
     * within the page.
     * 
     * @param x
     *            Coordinate in Java space
     * @param y
     *            Coordinate in Java space
     * @return y Coordinate in User space
     */
    public int cy(int x, int y)
    {
        return cxy(x, y)[1];
    }
    
    /**
     * This utility method converts the y coordinate from Java to User space
     * within the page.
     * 
     * @param x
     *            Coordinate in Java space
     * @param y
     *            Coordinate in Java space
     * @return x Coordinate in User space
     */
    public int cx(int x, int y)
    {
        return cxy(x, y)[0];
    }
    
    /**
     * This utility method converts the Java coordinates to User space within
     * the page.
     * 
     * @param x
     *            Coordinate in Java space
     * @param y
     *            Coordinate in Java space
     * @return array containing the x & y Coordinate in User space
     */
    public int[] cxy(int x, int y)
    {
        int r[] = new int[2];
        r[0] = x;
        r[1] = (int) pageFormat.getHeight() - y;
        return r;
    }
    
}
